using System;
using System.Collections.Generic;
using System.Text;

namespace Woz
{
    /// <summary>
    /// action manager class that manages actions also known as action factory
    /// </summary>
    public sealed class ActionManager
    {
        /// <summary>
        /// default constructor
        /// </summary>
        private ActionManager()
        {
            // instantiate the dictionaries of the undo and redo stacks
            this.mUndoStacks = new Dictionary<string, Stack<Woz.Action>>();
            this.mRedoStacks = new Dictionary<string, Stack<Woz.Action>>();

            // create a default stack to use
            this.AddStack("Default", true);
        }

        /// <summary>
        /// only instance of the action manager
        /// </summary>
        public static ActionManager Instance
        {
            get
            {
                if (sInstance == null)
                {
                    sInstance = new ActionManager();
                }

                return sInstance;
            }
        }

        public string CurrentStack
        {
            set
            {
                // make sure this stack exists
                if (!this.mUndoStacks.ContainsKey(value) || !this.mRedoStacks.ContainsKey(value))
                {
                    return;
                }

                // this stack exists so set it
                this.mCurrentUndoStack = this.mUndoStacks[value];
                this.mCurrentRedoStack = this.mRedoStacks[value];
            }
        }

        /// <summary>
        /// gets whether the action manager can undo
        /// </summary>
        public bool CanUndo
        {
            get
            {
                return (0 != this.mCurrentUndoStack.Count);
            }
        }

        /// <summary>
        /// gets whether the action manager can redo
        /// </summary>
        public bool CanRedo
        {
            get
            {
                return (0 != this.mCurrentRedoStack.Count);
            }
        }

        /// <summary>
        /// gets the last executed action or null if no such thing exists
        /// </summary>
        public Woz.Action LastExecutedAction
        {
            get
            {
                if (this.mCurrentUndoStack.Count > 0)
                {
                    return this.mCurrentUndoStack.Peek();
                }
                return null;
            }
        }

        /// <summary>
        /// pushes an action to the current undo stack
        /// </summary>
        /// <param name="pAction">the action to push</param>
        public void PushActionOnCurrentUndoStack(Woz.Action pAction)
        {
            // push this action to the current undo stack
            this.mCurrentUndoStack.Push(pAction);
        }

        /// <summary>
        /// pops an action from the current undo stack and returns it
        /// </summary>
        /// <returns>the action at the top of the current undo stack or null if there are none</returns>
        public Woz.Action PopActionFromCurrentUndoStack()
        {
            // check to see if the current undo stack is empty
            if (this.mCurrentUndoStack.Count > 0)
            {
                return this.mCurrentUndoStack.Pop();
            }

            // nothing found so return null
            return null;
        }

        /// <summary>
        /// creates a new undo and redo stack with the given name
        /// </summary>
        /// <param name="pStackName">the name of the stack to add</param>
        /// <param name="pSetAsCurrent">whether to set the stack as the current</param>
        public void AddStack(string pStackName, bool pSetAsCurrent)
        {
            // make sure this stack doesnt already exist
            if (this.mUndoStacks.ContainsKey(pStackName) || this.mRedoStacks.ContainsKey(pStackName))
            {
                return;
            }

            // this is a brand spanking new stack so go ahead and create it
            this.mUndoStacks[pStackName] = new Stack<Woz.Action>();
            this.mRedoStacks[pStackName] = new Stack<Woz.Action>();

            // set this as the current if they want us to
            if (pSetAsCurrent)
            {
                this.mCurrentUndoStack = this.mUndoStacks[pStackName];
                this.mCurrentRedoStack = this.mRedoStacks[pStackName];
            }
        }

        /// <summary>
        /// creates and returns an action of the passed in type but does not execute it
        /// </summary>
        /// <param name="pActionType">the type of action to create</param>
        /// <param name="pParams">arguments to the actions constructor</param>
        /// <returns>an instance of the passed in action type</returns>
        public Woz.Action CreateAction(Type pActionType, params object[] pParams)
        {
            // create an instance of this action using the activator
            Woz.Action action = Activator.CreateInstance(pActionType, pParams) as Woz.Action;

            // subscribe to its executed event
            action.Executed += new EventHandler(this.OnActionExecuted);

            // subscribe to its unexecuted event
            action.UnExecuted += new EventHandler(this.OnActionUnExecuted);

            // clear the redo stack
            this.mCurrentRedoStack.Clear();

            // send back this new action to be executed later
            return action;
        }

        /// <summary>
        /// undoes the last action
        /// </summary>
        public void Undo()
        {
            if (this.mCurrentUndoStack.Count > 0)
            {
                // tell the action on the top of the undo stack to unexecute itself
                this.mCurrentUndoStack.Peek().UnExecute();
            }
        }

        /// <summary>
        /// redoes the last action
        /// </summary>
        public void Redo()
        {
            if (this.mCurrentRedoStack.Count > 0)
            {
                // tell the action on the top of the redo stack to execute itself
                this.mCurrentRedoStack.Peek().Execute();
            }
        }

        /// <summary>
        /// reset the action manager
        /// </summary>
        public void Reset()
        {
            // clear the undo stacks
            foreach (Stack<Woz.Action> actionStack in this.mUndoStacks.Values)
            {
                actionStack.Clear();
            }

            // clear the redo stacks
            foreach (Stack<Woz.Action> actionStack in this.mRedoStacks.Values)
            {
                actionStack.Clear();
            }

            // fire the action redone event to signify that the undo stack has changed
            if (this.ActionRedone != null)
            {
                this.ActionRedone.Invoke(string.Empty, string.Empty);
            }

            // fire the action undone event to signify that the undo stack has changed
            if (this.ActionUndone != null)
            {
                this.ActionUndone.Invoke(string.Empty, string.Empty);
            }
        }

        /// <summary>
        /// returns a list of all the actions
        /// </summary>
        /// <returns>a list of all the actions</returns>
        public Woz.Action[] GetActions()
        {
            return this.mCurrentUndoStack.ToArray();
        }

        /// <summary>
        /// handles when an action is executed
        /// </summary>
        /// <param name="pSender">the action that fired this event</param>
        /// <param name="pEventArgs">the event args</param>
        private void OnActionExecuted(object pSender, EventArgs pEventArgs)
        {
            // get this action from the parameter
            Woz.Action action = pSender as Woz.Action;

            // save the old tool name and new tool name
            string oldToolName, newToolName;
            
            // check if we are executing an action in the redo stack
            if ((this.mCurrentRedoStack.Count > 0) && (this.mCurrentRedoStack.Peek() == action))
            {
                // the old one is the one on the undo stack
                oldToolName = this.GetUndoToolName();

                // the new one is the one on the redo stack
                newToolName = this.GetRedoToolName();

                // transfer the action on the top of the redo stack to the top of the undo stack
                this.mCurrentUndoStack.Push(this.mCurrentRedoStack.Pop());
            }

            else
            {
                // the new tool name comes from the action itself
                newToolName = action.ToolName;

                // the old one is the one on the undo stack
                oldToolName = this.GetUndoToolName();

                // add this action to the list of undo actions
                this.mCurrentUndoStack.Push(action);
            }

            // fire the action redone event
            if (this.ActionRedone != null)
            {
                this.ActionRedone.Invoke(newToolName, oldToolName);
            }
        }

        /// <summary>
        /// handles when an action is unexecuted
        /// </summary>
        /// <param name="pSender">the action that fired this event</param>
        /// <param name="pEventArgs">the event args</param>
        private void OnActionUnExecuted(object pSender, EventArgs pEventArgs)
        {
            // the new tool name is on the undo stack
            string newToolName = this.GetUndoToolName();

            // the old one is the one on the redo stack
            string oldToolName = this.GetRedoToolName();

            // remove this item from the undo list
            this.mCurrentRedoStack.Push(this.mCurrentUndoStack.Pop());

            // fire the action undone event
            if (this.ActionUndone != null)
            {
                this.ActionUndone.Invoke(newToolName, oldToolName);
            }
        }

        /// <summary>
        /// gets the tool name of the action at the top of the undo stack
        /// </summary>
        /// <returns>the tool name or an empty string</returns>
        private string GetUndoToolName()
        {
            // if the undo stack is not empty
            if (this.mCurrentUndoStack.Count > 0)
            {
                // get the tool name of the top action on the undo stack
                return this.mCurrentUndoStack.Peek().ToolName;
            }

            // otherwise nothing
            return string.Empty;
        }

        /// <summary>
        /// gets the tool name of the action at the top of the redo stack
        /// </summary>
        /// <returns>the tool name or an empty string</returns>
        private string GetRedoToolName()
        {
            // if the redo stack is not empty
            if (this.mCurrentRedoStack.Count > 0)
            {
                // get the tool name of the top action on the redo stack
                return this.mCurrentRedoStack.Peek().ToolName;
            }

            // otherwise nothing
            return string.Empty;
        }

        /// <summary>
        /// represents the method that will handle when an action is undone or redone
        /// </summary>
        /// <param name="pNewToolName">the new actions tool name</param>
        /// <param name="pOldToolName">the old actions tool name</param>
        public delegate void ActionDoneHandler(string pNewToolName, string pOldToolName);

        /// <summary>
        /// fires when an action has been undone
        /// </summary>
        public event ActionDoneHandler ActionUndone;

        /// <summary>
        /// fires when an action has been redone
        /// </summary>
        public event ActionDoneHandler ActionRedone;

        /// <summary>
        /// the single instance of the action manager
        /// </summary>
        private static ActionManager sInstance;

        /// <summary>
        /// dictionary of undo stacks of actions
        /// </summary>
        private Dictionary<string, Stack<Woz.Action>> mUndoStacks;

        /// <summary>
        /// dictionary of redo stacks of actions
        /// </summary>
        private Dictionary<string, Stack<Woz.Action>> mRedoStacks;

        /// <summary>
        /// represents the undo stack that is currently being used
        /// </summary>
        private Stack<Woz.Action> mCurrentUndoStack;

        /// <summary>
        /// represents the redo stack that is currently being used
        /// </summary>
        private Stack<Woz.Action> mCurrentRedoStack;
    }
}
